using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing.Imaging;
using System.Drawing;
using System.IO;
using H2.DataTypes;
using System.Runtime.InteropServices;

namespace H2.Sections
{
    public class BitmapCollection
    {
        private Bitmap[] BitmapCache;
        private MapStream Map;

        internal BitmapCollection(MapStream map, int offset)
        {
            int Count = map.ReadReflexiveAt(offset + 68);
            BitmapCache = new Bitmap[Count];
            for(int i = 0; i < Count; i++)
                BitmapCache[i] = new Bitmap(map, (int)map.Position + (i * 116));

            Map = map;
        }

        public Bitmap this[int index] { get { return BitmapCache[index]; } set { BitmapCache[index] = value; } }

        public int Count { get { return BitmapCache.Length; } }

        public int IndexOf(Bitmap bitmap)
        {
            List<Bitmap> bitmaps = new List<Bitmap>(BitmapCache);
            return bitmaps.IndexOf(bitmap);
        }

        public void Internalise(BinaryReader MainMenu, BinaryReader Shared, BinaryReader SpShared)
        {
            Map.Status = "Getting Bitmap Data...";
            BitmapDataChunk[] RawData = new BitmapDataChunk[BitmapCache.Length];

            int offset = this[0].LOD1Offset;
            BinaryReader map;
            #region SelectMap
            long Pointer = (int)offset & 0XC0000000;
            offset &= (int)0X3FFFFFFF;
            switch (Pointer)
            {
                case 0:
                    map = new BinaryReader(Map.BaseStream);
                    break;
                case 0X80000000:
                    map = Shared;
                    break;
                case 0XC0000000:
                    map = SpShared;
                    break;
                case 0X40000000:
                    map = MainMenu;
                    break;
                default:
                    return;
            }
            #endregion

            int TotalPadding = 0;

            for (int i = 0; i < BitmapCache.Length; i++)
            {
                RawData[i] = new BitmapDataChunk();

                //Lod 1
                map.BaseStream.Position = this[i].LOD1Offset & (int)0X3FFFFFFF;
                RawData[i].LOD1 = map.ReadBytes(this[i].LOD1Size);

                //Lod 2
                map.BaseStream.Position = this[i].LOD2Offset & (int)0X3FFFFFFF;
                RawData[i].LOD2 = map.ReadBytes(this[i].LOD2Size);

                //Lod 3
                map.BaseStream.Position = this[i].LOD3Offset & (int)0X3FFFFFFF;
                RawData[i].LOD3 = map.ReadBytes(this[i].LOD3Size);

                TotalPadding += Map.CalculatePaddingSize(RawData[i].LOD1.Length + RawData[i].LOD2.Length + RawData[i].LOD1.Length, 512);
            }

            int TotalSize = TotalPadding;
            foreach (BitmapDataChunk b in RawData)
                TotalSize += b.LOD1.Length + b.LOD2.Length + b.LOD3.Length;

            //Add Data
            Map.BaseStream.SetLength(Map.Length + TotalSize);
            Map.Header.Filesize += TotalSize;

            Map.Status = "Moving Data...";
            //Move Data
            int StartOffset = Map.Header.IndexOffset;
            Map.WriteAt(StartOffset + TotalSize, Map.ReadBytesAt(StartOffset, (int)Map.Length - StartOffset, true), true);
            Map.Header.IndexOffset = StartOffset + TotalSize;

            Map.Status = "Writing Bitmap Data...";
            //Write Data
            Map.Position = StartOffset;
            for (int i = 0; i < RawData.Length; i++)
            {
                this[i].ChunkStartOffset += TotalSize;

                //Lod 1
                this[i].LOD1Offset = (int)Map.Position;
                Map.Write(RawData[i].LOD1);
                
                //Lod 2
                this[i].LOD2Offset = (int)Map.Position;
                Map.Write(RawData[i].LOD2);

                //Lod 3
                this[i].LOD3Offset = (int)Map.Position;
                Map.Write(RawData[i].LOD3);

                Map.Write(new byte[Map.CalculatePaddingSize(RawData[i].LOD1.Length + RawData[i].LOD2.Length + RawData[i].LOD1.Length, 512)]);
            }

            Map.Flush();
            Map.Reload();
            Map.Status = "Bitmap Internalised Successfully.";
        }

        public class BitmapDataChunk
        {
            public byte[] LOD1;
            public byte[] LOD2;
            public byte[] LOD3;
        }
    }

    public class Bitmap : IComparable<Bitmap>
    {
        private MapStream Map;
        internal int ChunkStartOffset;

        internal Bitmap(MapStream map, int chunkStartOffset)
        {
            Map = map;
            ChunkStartOffset = chunkStartOffset;
        }

        public System.Drawing.Bitmap LoadPreview(BinaryReader MainMenu, BinaryReader Shared, BinaryReader SpShared)
        { return LoadPreview(MainMenu, Shared, SpShared, 0, 0); }

        public System.Drawing.Bitmap LoadPreview(BinaryReader MainMenu, BinaryReader Shared, BinaryReader SpShared, int SBSPIndex, int PaletteIndex)
        {
            int width = Width;
            int height = Height;
            int offset = LOD1Offset;
            long Pointer = (int)offset & 0XC0000000;
            offset &= (int)0X3FFFFFFF;
            BinaryReader map;
            #region SelectMap
            switch (Pointer)
            {
                case 0:
                    map = new BinaryReader(Map.BaseStream);
                    break;
                case 0X80000000:
                    map = Shared;
                    break;
                case 0XC0000000:
                    map = SpShared;
                    break;
                case 0X40000000:
                    map = MainMenu;
                    break;
                default:
                    return null;
            }
            #endregion

            byte[] DecodedLOD;
            PixelFormat pixelFormat;
            int LengthMultipier;
            #region Decode
            int HalfSourceDataLength;
            byte[] RecodedLOD;
            if (width % 8 != 0)
                width = (ushort)(width + (8 - (width % 8)));
            map.BaseStream.Position = offset;
            DecodedLOD = map.ReadBytes(LOD1Size);
            if (DecodedLOD.Length == 0) return new System.Drawing.Bitmap(1, 1);
            switch (Format)
            {
                case FormatEnum.DXT1://Working
                    DecodedLOD = H2.Sections.Bitmaps.DecodeDXT1(width, height, DecodedLOD);
                    pixelFormat = PixelFormat.Format32bppArgb;
                    LengthMultipier = 4;
                    break;
                case FormatEnum.DXT2And3://Working
                    DecodedLOD = H2.Sections.Bitmaps.DecodeDXT23(width, height, DecodedLOD);
                    pixelFormat = PixelFormat.Format32bppArgb;
                    LengthMultipier = 4;
                    break;
                case FormatEnum.DXT4And5://Working
                    DecodedLOD = H2.Sections.Bitmaps.DecodeDXT45(width, height, DecodedLOD);
                    pixelFormat = PixelFormat.Format32bppArgb;
                    LengthMultipier = 4;
                    break;
                case FormatEnum.A8R8G8B8://Working?
                    if (Swizzle == true)
                        DecodedLOD = Bitmaps.Swizzle(DecodedLOD, width, height, Depth, 32, true);
                    pixelFormat = PixelFormat.Format32bppArgb;
                    LengthMultipier = 4;
                    break;
                case FormatEnum.X8R8G8B8://Working
                    if (Swizzle == true)
                        DecodedLOD = Bitmaps.Swizzle(DecodedLOD, width, height, -1, 32, true);
                    pixelFormat = PixelFormat.Format32bppRgb;
                    LengthMultipier = 4;
                    break;
                case FormatEnum.A4R4G4B4://Working
                    if (Swizzle == true)
                        DecodedLOD = Bitmaps.Swizzle(DecodedLOD, PixelOffset, width, height, -1, 16, true);
                    LengthMultipier = 2;
                    pixelFormat = PixelFormat.Format32bppArgb;
                    int QuaterSourceDataLength = DecodedLOD.Length / 4;
                    RecodedLOD = new byte[DecodedLOD.Length * 2];
                    for (int i = 0; i < QuaterSourceDataLength; i++)
                    {
                        int a = i * 4;
                        RecodedLOD[a] = (byte)(DecodedLOD[a] & 0XF0);
                        RecodedLOD[a + 1] = (byte)(DecodedLOD[a] & 0X0F);
                        RecodedLOD[a + 2] = (byte)(DecodedLOD[a + 1] & 0XF0);
                        RecodedLOD[a + 3] = (byte)(DecodedLOD[a + 1] & 0X0F);
                    }
                    DecodedLOD = RecodedLOD;
                    break;
                case FormatEnum.Unknown://Working
                    if (Swizzle == true)
                        DecodedLOD = Bitmaps.Swizzle(DecodedLOD, 0, width, height, -1, 16, true);
                    LengthMultipier = 2;
                    pixelFormat = PixelFormat.Format32bppArgb;
                    HalfSourceDataLength = DecodedLOD.Length / 2;
                    RecodedLOD = new byte[HalfSourceDataLength * 4];
                    for (int i = 0; i < HalfSourceDataLength; i++)
                    {
                        int a = i * 2;
                        RecodedLOD[a * 2] = DecodedLOD[a + 1];
                        RecodedLOD[(a * 2) + 1] = DecodedLOD[a + 1];
                        RecodedLOD[(a * 2) + 2] = DecodedLOD[a + 1];
                        RecodedLOD[(a * 2) + 3] = DecodedLOD[a];
                    }
                    DecodedLOD = RecodedLOD;
                    break;
                case FormatEnum.A1R5G5B5://Working
                    if (Swizzle == true)
                        DecodedLOD = Bitmaps.Swizzle(DecodedLOD, PixelOffset, width, height, -1, 16, true);
                    LengthMultipier = 2;
                    pixelFormat = PixelFormat.Format16bppArgb1555;
                    break;
                case FormatEnum.R5G6B5://Working
                    if (Swizzle == true)
                        DecodedLOD = Bitmaps.Swizzle(DecodedLOD, PixelOffset, width, height, -1, 16, true);
                    LengthMultipier = 2;
                    pixelFormat = PixelFormat.Format16bppRgb565;
                    break;
                case FormatEnum.A8Y8://Working
                    if (Swizzle == true)
                        DecodedLOD = Bitmaps.Swizzle(DecodedLOD, PixelOffset, width, height, Depth, 16, true);
                    LengthMultipier = 4;
                    pixelFormat = PixelFormat.Format32bppArgb;
                    HalfSourceDataLength = DecodedLOD.Length / 2;
                    RecodedLOD = new byte[HalfSourceDataLength * 4];
                    for (int i = 0; i < HalfSourceDataLength; i++)
                    {
                        int a = i * 2;
                        RecodedLOD[a] = DecodedLOD[a];
                        RecodedLOD[a + 1] = DecodedLOD[a];
                        RecodedLOD[a + 2] = DecodedLOD[a];
                        RecodedLOD[a + 3] = DecodedLOD[a + 1];
                    }
                    DecodedLOD = RecodedLOD;
                    break;
                case FormatEnum.P8://Working
                    if (Swizzle == true)
                        DecodedLOD = Bitmaps.Swizzle(DecodedLOD, PixelOffset, width, height, Depth, 8, true);
                    LengthMultipier = 4;
                    pixelFormat = PixelFormat.Format32bppArgb;

                    if (!Bitmaps.H2PaletteLoaded) Bitmaps.LoadHalo2Palette();

                    RecodedLOD = new byte[DecodedLOD.Length * 4];
                    for (int i = 0; i < DecodedLOD.Length; i++)
                    {
                        int a = i * 4;
                        RecodedLOD[a] = Bitmaps.H2Palette[DecodedLOD[i]].R;
                        RecodedLOD[a + 1] = Bitmaps.H2Palette[DecodedLOD[i]].G;
                        RecodedLOD[a + 2] = Bitmaps.H2Palette[DecodedLOD[i]].B;
                        RecodedLOD[a + 3] = Bitmaps.H2Palette[DecodedLOD[i]].A;
                    }
                    DecodedLOD = RecodedLOD;
                    break;
                case FormatEnum.Y8://Working
                    if (Swizzle == true)
                        DecodedLOD = Bitmaps.Swizzle(DecodedLOD, PixelOffset, width, height, Depth, 8, true);
                    LengthMultipier = 4;
                    pixelFormat = PixelFormat.Format32bppArgb;

                    RecodedLOD = new byte[DecodedLOD.Length * 4];
                    for (int i = 0; i < DecodedLOD.Length; i++)
                    {
                        int a = i * 4;
                        RecodedLOD[a] = DecodedLOD[i];
                        RecodedLOD[a + 1] = DecodedLOD[i];
                        RecodedLOD[a + 2] = DecodedLOD[i];
                        RecodedLOD[a + 3] = 255;
                    }
                    DecodedLOD = RecodedLOD;
                    break;
                case FormatEnum.Lightmap://Working
                    if (Swizzle)
                        DecodedLOD = Bitmaps.Swizzle(DecodedLOD, PixelOffset, width, height, Depth, 8, true);
                    LengthMultipier = 4;
                    pixelFormat = PixelFormat.Format32bppArgb;

                    RecodedLOD = new byte[DecodedLOD.Length * 4];
                    LightMap.Palette Palette = Map.Sbsp[SBSPIndex, Tags.SearchType.Index].LightMap.Palettes[PaletteIndex];
                    for (int i = 0; i < DecodedLOD.Length; i++)
                    {
                        int a = i * 4;
                        RecodedLOD[a + 0] = Palette.Colors[DecodedLOD[i]].R;
                        RecodedLOD[a + 1] = Palette.Colors[DecodedLOD[i]].G;
                        RecodedLOD[a + 2] = Palette.Colors[DecodedLOD[i]].B;
                        RecodedLOD[a + 3] = Palette.Colors[DecodedLOD[i]].A;
                    }
                    DecodedLOD = RecodedLOD;
                    break;
                //case FormatEnum.AY8:
                //    if (Swizzle == true)
                //        DecodedLOD = Bitmaps.Swizzle(DecodedLOD, PixelOffset, width, height, Depth, 8, true);
                //    LengthMultipier = 4;
                //    pixelFormat = PixelFormat.Format32bppArgb;

                //    RecodedLOD = new byte[DecodedLOD.Length * 4];
                //    for (int i = 0; i < DecodedLOD.Length; i++)
                //    {
                //        int a = i * 4;
                //        RecodedLOD[a] = DecodedLOD[i];
                //        RecodedLOD[a + 1] = DecodedLOD[i];
                //        RecodedLOD[a + 2] = DecodedLOD[i];
                //        RecodedLOD[a + 3] = 255;
                //    }
                //    DecodedLOD = RecodedLOD;
                //    break;
                case FormatEnum.A8://Working
                    if (Swizzle == true)
                        DecodedLOD = Bitmaps.Swizzle(DecodedLOD, PixelOffset, width, height, Depth, 8, true);
                    LengthMultipier = 4;
                    pixelFormat = PixelFormat.Format32bppArgb;

                    RecodedLOD = new byte[DecodedLOD.Length * 4];
                    for (int i = 0; i < DecodedLOD.Length; i++)
                    {
                        int a = i * 4;
                        RecodedLOD[a] = 255;
                        RecodedLOD[a + 1] = 255;
                        RecodedLOD[a + 2] = 255;
                        RecodedLOD[a + 3] = DecodedLOD[i];
                    }
                    DecodedLOD = RecodedLOD;
                    break;
                default:
                    return new System.Drawing.Bitmap(1, 1);
            }
            #endregion

            System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(width, height);
            BitmapData data = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, pixelFormat);
            Marshal.Copy(DecodedLOD, 0, data.Scan0, width * height * LengthMultipier);
            bitmap.UnlockBits(data);
            return bitmap;
        }

        public enum FormatEnum
        {
            A8 = 0,
            //BITM_TYPE_2D = 0,
            Y8 = 1,
            //BITM_TYPE_3D = 1,
            AY8 = 2,
            //BITM_TYPE_CUBEMAP = 2,
            A8Y8 = 3,
            R5G6B5 = 6,
            A1R5G5B5 = 8,
            A4R4G4B4 = 9,
            X8R8G8B8 = 10,
            A8R8G8B8 = 11,
            DXT1 = 14,
            DXT2And3 = 15,
            DXT4And5 = 16,
            P8 = 17,
            Lightmap = 18,
            Unknown = 22
        }

        public short Width { get { return Map.ReadInt16At(ChunkStartOffset + 4, true); } set { Map.WriteAt(ChunkStartOffset + 4, value, true); } }
        public short Height { get { return Map.ReadInt16At(ChunkStartOffset + 6, true); } set { Map.WriteAt(ChunkStartOffset + 6, value, true); } }
        public short Depth { get { return Map.ReadInt16At(ChunkStartOffset + 8, true); } set { Map.WriteAt(ChunkStartOffset + 8, value, true); } }
        public short Type { get { return Map.ReadInt16At(ChunkStartOffset + 10, true); } set { Map.WriteAt(ChunkStartOffset + 10, value, true); } }
        public FormatEnum Format { get { return (FormatEnum)Map.ReadInt16At(ChunkStartOffset + 12, true); } set { Map.WriteAt(ChunkStartOffset + 12, (int)value, true); } }
        public Bitmask Flags
        { 
            get 
            {
                Bitmask flags = new Bitmask(new string[] { "^2 Dimensions", "Compressed", "Palettized", "Swizzled", "Linear", "v16u16", "MIP Map Debug Level", "Prefer Stutter (Prefer Low Detail)", "Interlaced?" }, 2);
                flags.SetAllData(Map.ReadBytesAt(ChunkStartOffset + 14, 2, true));
                return flags;
            } 
            set 
            {
                Map.WriteAt(ChunkStartOffset, value, true);
            }
        }
        public bool Swizzle { get { return Flags.Bits[3]; } }
        public short RegPointX { get { return Map.ReadInt16At(ChunkStartOffset + 14, true); } set { Map.WriteAt(ChunkStartOffset + 14, value, true); } }
        public short RegPointY { get { return Map.ReadInt16At(ChunkStartOffset + 16, true); } set { Map.WriteAt(ChunkStartOffset + 16, value, true); } }
        public short MipMapCount { get { return Map.ReadInt16At(ChunkStartOffset + 18, true); } set { Map.WriteAt(ChunkStartOffset + 18, value, true); } }
        public short PixelOffset { get { return Map.ReadInt16At(ChunkStartOffset + 20, true); } set { Map.WriteAt(ChunkStartOffset + 20, value, true); } }
        public int LOD1Offset { get { return Map.ReadInt32At(ChunkStartOffset + 28, true); } set { Map.WriteAt(ChunkStartOffset + 28, value, true); } }
        public int LOD2Offset { get { return Map.ReadInt32At(ChunkStartOffset + 32, true); } set { Map.WriteAt(ChunkStartOffset + 32, value, true); } }
        public int LOD3Offset { get { return Map.ReadInt32At(ChunkStartOffset + 36, true); } set { Map.WriteAt(ChunkStartOffset + 36, value, true); } }
        public int LOD1Size { get { return Map.ReadInt32At(ChunkStartOffset + 52, true); } set { Map.WriteAt(ChunkStartOffset + 52, value, true); } }
        public int LOD2Size { get { return Map.ReadInt32At(ChunkStartOffset + 56, true); } set { Map.WriteAt(ChunkStartOffset + 56, value, true); } }
        public int LOD3Size { get { return Map.ReadInt32At(ChunkStartOffset + 60, true); } set { Map.WriteAt(ChunkStartOffset + 60, value, true); } }
        public RawLocations RawLocation
        {
            get
            {
                long Pointer = LOD1Offset & 0XC0000000;
                switch (Pointer)
                {
                    case 0:
                        return RawLocations.Internal;
                    case 0X80000000:
                        return RawLocations.Shared;
                    case 0XC0000000:
                        return RawLocations.SinglePlayerShared;
                    case 0X40000000:
                        return RawLocations.MainMenu;
                    default:
                        return RawLocations.Unknown;
                }
            }
        }

        #region IComparable<Bitmap> Members

        public int CompareTo(Bitmap other)
        {
            return ChunkStartOffset.CompareTo(other.ChunkStartOffset);
        }

        #endregion
    }
}
